package AutoTraderFraudCalculator;
import java.util.*;

/**
 * Provides methods of analysis for calculating the risk of a listing on
 * <code>autotrader.ca</code> of being a scam or not. Calculations are based on
 * a salesclerk's experience and expertise toward the manner.
 * 
 * <p> The required object needed for analysis is a <code>CarListings</code> object
 * {@link CarListings}. There is no constructor classes for <code>Analysis
 * </code> but in order to do calculations, <code>initCalculations</code> must
 * be called.
 * 
 * <p> @TODO Exceptions
 */
public class Analysis 
{
    /**
     * Stores the average price of all the listings that <code>initCalculations
     * </code> was called with.
     */
    private static double avgPrice;
    
    /**
     * Stores the maximum price of all the listings that <code>initCalculations
     * </code> was called with.
     */
    private static int maxPrice;
    
    /**
     * Stores the minimum price of all the listings that <code>initCalculations
     * </code> was called with.
     */
    private static int minPrice;
    
    /**
     * Stores the average mileage of all the listings that <code>
     * initCalculations</code> was called with.
     */
    private static double avgMileage;
    
    /**
     * Stores the maximum mileage of all the listings that <code>
     * initCalculations</code> was called with.
     */
    private static int maxMileage;
    
    /**
     * Stores the minimum mileage of all the listings that <code>
     * initCalculations</code> was called with.
     */
    private static int minMileage;
    
    /**
     * Stores the weight associated with having a phone number or not. The
     * values that are possible are <code>0</code> and <code>25</code>.
     */
    private static int phoneNumRisk; // weight: 25
    
    /**
     * Stores the weight associated with the price. Takes into account the
     * mileage and price information and calculates according to a metric. The
     * values can range between <code>0</code> and <code>55</code>.
     */
    private static int priceRisk; // weight: 55
    
    /**
     * Stores the weight associated with popularity risk. To be specific, the
     * less listings there are, the less accurate the total risk will be. The
     * values can range between <code>0</code and <code>10</code>.
     */
    private static int popularityRisk; // weight: 10
    
    /**
     * Stores the risk associated with the privacy of the listing. The values
     * can range between <code>0</code> and <code>10</code>.
     */
    private static int listingTypeRisk; // weight: 10
    
    /**
     * Stores the total risk of a particular listing.
     */
    private static int totalRisk;
    
    /**
     * Performs initial calculations which involves setting the average price,
     * average milage and the popularity risk.
     * 
     * @param carAds A list that contains all the listing to be analyzed.
     * @return The same list with an additional risk factor specification.
     */
    public CarListings initCalculations(CarListings carAds)
    {
        long sumPrices = 0;
        long sumMileage = 0;
        int price = 0;
        int minPriced = 0;
        int maxPriced = 0;
        int mileage = 0;
        int minMileaged = 0;
        int maxMileaged = 0;
        int entries = 0;
        int numListings = carAds.getSize();
        boolean hasPrice = false;
        boolean hasMileage = false;
        phoneNumRisk = 0;
        priceRisk = 0;
        popularityRisk = 0;
        listingTypeRisk = 0;
        totalRisk = 0;
        
        // the more listings, the more accurate the calculations = more chance
        // of being risky
        if (numListings >= 0 && numListings <= 99)
        {
            popularityRisk = 0;
        }
        else if (numListings >= 100 && numListings <= 199)
        {
            popularityRisk = 1;
        }
        else if (numListings >= 200 && numListings <= 299)
        {
            popularityRisk = 2;
        }
        else if (numListings >= 300 && numListings <= 399)
        {
            popularityRisk = 3;
        }
        else if (numListings >= 400 && numListings <= 499)
        {
            popularityRisk = 4;
        }
        else if (numListings >= 500 && numListings <= 599)
        {
            popularityRisk = 5;
        }
        else if (numListings >= 600 && numListings <= 699)
        {
            popularityRisk = 6;
        }
        else if (numListings >= 700 && numListings <= 799)
        {
            popularityRisk = 7;
        }
        else if (numListings >= 800 && numListings <= 899)
        {
            popularityRisk = 8;
        }
        else if (numListings >= 900 && numListings <= 999)
        {
            popularityRisk = 9;
        }
        else if (numListings >= 1000)
        {
            popularityRisk = 10;
        }  
        
        for (int i = 0; i < carAds.getSize(); i++)
        { 
            if (!carAds.getAd(i).getSpecs().get("Mileage:").contentEquals("N/A"))
            {
                mileage = Integer.parseInt(carAds.getAd(i).getSpecs().
                                        get("Mileage:").split(" ")[0]);
                
                hasMileage = true;
            }
            
            if (!carAds.getAd(i).getSpecs().get("Price:").contentEquals("N/A"))
            {
                price = Integer.parseInt(carAds.getAd(i).getSpecs().
                                                                get("Price:"));
                
                hasPrice = true;
            }
            
            if (hasMileage && hasPrice)
            {
                sumPrices += price;
                sumMileage += mileage;
                entries++;
                
                if (price > maxPriced)
                {
                    maxPriced = price;
                }
                else if (price < minPriced)
                {
                    minPriced = price;
                }
                else if (maxPriced == 0)
                {
                    maxPriced = price;
                    minPriced = price;
                }
                
                if (maxMileaged == 0)
                {
                    maxMileaged = price;
                    minMileaged = price;
                }
                else if (mileage > maxMileaged)
                {
                    maxMileaged = mileage;
                }
                else if (mileage < minMileaged)
                {
                    minMileaged = mileage;
                }
            }
            
            hasPrice = false;
            hasMileage = false;
            price = 0;
            mileage = 0;
        }
        
        // boundary case
        if (entries == 0)
        {
            entries++;
        }
        
        minPrice = minPriced;
        avgPrice = (sumPrices / entries);
        maxPrice = maxPriced;
        minMileage = minMileaged;
        avgMileage = (sumMileage / entries);
        maxMileage = maxMileaged;
        
        carAds = getTotalRisk(carAds);
        return (new CarListings(carAds));
    }
    
    /**
     * Calculates the total risk each listing in the list of car ads and adds
     * a risk factor specification to each listing. 
     * 
     * @param carAds A list with every car ad to be analyzed.
     * @return A list with the same car ads with an additional specification 
     *         that shows the risk factor. Can be obtained by <code>Risk:</code>
     *         as the type when using <code>getSpecs()</code>.
     * @see Listing#getSpecs() 
     */
    public CarListings getTotalRisk(CarListings carAds)
    {
        double priceMargin = 0;
        double pivotMileage = 0;
        double mileageMargin = 0;
        int price = 0;
        int mileage = 0;
        boolean hasPrice = false;
        boolean hasMileage = false;
        boolean hasPhoneNum = true;
        boolean isPrivateListing = false;
        
        for (int i = 0; i < carAds.getSize(); i++)
        {
            hasPrice = false;
            hasMileage = false;
            hasPhoneNum = true;
            isPrivateListing = false;
            price = 0;
            mileage = 0;
            phoneNumRisk = 0;
            priceRisk = 0;
            listingTypeRisk = 0;
            totalRisk = 0;
            
            // if no contact number, more chance of scam
            if (carAds.getAd(i).getSpecs().get("Contact Number:").
                                                        contentEquals("N/A"))
            {
                phoneNumRisk = 25;
                hasPhoneNum = false;
            }
            
            if (!carAds.getAd(i).getSpecs().get("Mileage:").contentEquals("N/A"))
            {
                mileage = Integer.parseInt(carAds.getAd(i).getSpecs().
                                        get("Mileage:").split(" ")[0]);
                
                hasMileage = true;
            }
            
            if (!carAds.getAd(i).getSpecs().get("Price:").contentEquals("N/A"))
            {
                price = Integer.parseInt(carAds.getAd(i).getSpecs().
                                                                get("Price:"));
                
                hasPrice = true;
            }
            
            // if a private listing, adds to the risk factor
            if (carAds.getAd(i).getSpecs().get("Type of Listing:").
                                            contentEquals("Private Listing"))
            {
                listingTypeRisk = 10;
                isPrivateListing = true;
            }
            
            // different risk parameters
            if (!hasMileage && !hasPrice && isPrivateListing && !hasPhoneNum)
            {
                priceRisk = 55;
            }
            else if (!hasMileage && !hasPrice && isPrivateListing)
            {
                priceRisk = 44;
            }
            else if (!hasMileage && hasPrice && isPrivateListing)
            {
                priceRisk = 28;
            }
            else if (hasMileage && !hasPrice && isPrivateListing)
            {
                priceRisk = 28;
            }
            else if ((!hasMileage && !isPrivateListing) ||
                    (!hasPrice && !isPrivateListing))
            {
                priceRisk = 0;
            }
            else
            {
                // first figure out the "pivot" milage point which is dependent
                // on the price margin (how far the price is from the average
                // price) then depending on how far UNDER the milage is from
                // the pivot milage, the priceRisk is calculated with a weight
                // of 55, rounded up
                priceMargin = ((price / avgPrice) - 1);
                
                if (priceMargin != 0)
                {
                    pivotMileage = (avgMileage - (avgMileage * priceMargin));
                }
                else if (priceMargin == 0)
                {
                    pivotMileage = avgMileage;
                }
                
                if (mileage >= pivotMileage)
                {
                    priceRisk = 0;
                }
                else if (mileage < pivotMileage)
                {
                    mileageMargin = (1 - (mileage / pivotMileage));
                    priceRisk = (int)((mileageMargin * 55) + 0.5);
                }
                
                if (!isPrivateListing)
                {
                    priceRisk = (int)(priceRisk * 0.5);
                }
            }
            
            totalRisk = phoneNumRisk + priceRisk + 
                                            popularityRisk + listingTypeRisk;
            
            //No risk calculation can be 100% because knowing if a listing
            //is a scam for certainty is impossible.
            if (totalRisk == 100)
            {
                totalRisk = 99;
            }
            
            carAds.getAd(i).setSpecification("Risk:", String.valueOf(totalRisk));
        }
        return (new CarListings(carAds));
    }
    
    /**
     * Retrieve the minimum price of the listings.
     * 
     * @return The minimum price of all the listings.
     */
    public int getMinPrice()
    {
        return minPrice;
    }
    
    /**
     * Retrieve the average price of all the listings.
     * 
     * @return The average price of all the listings.
     */
    public int getAvgPrice()
    {
        return (int)(avgPrice + 0.5);
    }
    
    /**
     * Retrieve the maximum price of all the listings.
     * 
     * @return The maximum price of all the listings.
     */
    public int getMaxPrice()
    {
        return maxPrice;
    }
    
    /**
     * Retrieve the minimum mileage of all the listings.
     * 
     * @return The minimum mileage of all the listings.
     */
    public int getMinMileage()
    {
        return minMileage;
    }
    
    /**
     * Retrieve the average mileage of all the listings.
     * 
     * @return The average mileage of all the listings.
     */
    public int getAvgMileage()
    {
        return (int)(avgMileage + 0.5);
    }
    
    /**
     * Retrieve the maximum mileage of all the listings.
     * 
     * @return The maximum mileage of all the listings.
     */
    public int getMaxMileage()
    {
        return maxMileage;
    }
}